// $Id$

// Copyright by Johannes Sixt
// This file is under GPL, the GNU General Public Licence

#include <qdir.h>
#include <q3ptrlist.h>
#include <Q3StrList>
#include <kglobal.h>
#include <kstandarddirs.h>
#include <kconfig.h>
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "typetable.h"
#include "mydebug.h"

// the TypeTables of all known libraries
static QList<TypeTable*> typeTables;
bool typeTablesInited = false;


// the unknown type
TypeInfo TypeInfo::m_unknownType("");


void TypeTable::initTypeLibraries()
{
	if (!typeTablesInited) 
	{
		TypeTable::loadTypeTables();
	}
}

void TypeTable::loadTypeTables()
{
	typeTablesInited = true;
	
	const QStringList files = KGlobal::dirs()->findAllResources("types", "*.kdbgtt");
	
	if (files.isEmpty()) 
	{
		TRACE("no type tables found");
		return;
	}
	
	QString fileName;
	QListIterator<QString> it(files);
	while (it.hasNext())
	{
		fileName = it.next();
		TypeTable* newTable = new TypeTable;
		newTable->loadFromFile(fileName);
		typeTables.append(newTable);
	}
}


TypeTable::TypeTable() :
	m_printQStringDataCmd(0)
{
    m_typeDict.setAutoDelete(true);
    // aliasDict keeps only pointers to items into typeDict
    m_aliasDict.setAutoDelete(false);
}

TypeTable::~TypeTable()
{
    delete[] m_printQStringDataCmd;
}


static const char TypeTableGroup[] = "Type Table";
static const char LibDisplayName[] = "LibDisplayName";
static const char ShlibRE[] = "ShlibRE";
static const char EnableBuiltin[] = "EnableBuiltin";
static const char PrintQStringCmd[] = "PrintQStringCmd";
static const char TypesEntryFmt[] = "Types%d";
static const char DisplayEntry[] = "Display";
static const char AliasEntry[] = "Alias";
static const char ExprEntryFmt[] = "Expr%d";
static const char FunctionGuardEntryFmt[] = "FunctionGuard%d";


void TypeTable::loadFromFile(const QString& fileName)
{
	TRACE("reading file " + fileName);
	KConfig cf(fileName, KConfig::OnlyLocal);
	
	/*
	* Read library name and properties.
	*/
	cf.setGroup(TypeTableGroup);
	m_displayName = cf.readEntry(LibDisplayName);
	if (m_displayName.isEmpty()) 
	{
		// use file name instead
		int slash = fileName.findRev('\\');
		if (slash >= 0)
			m_displayName =	 fileName.mid(slash+1, fileName.length());
		else
			m_displayName = fileName;
		int dot = m_displayName.findRev('.');
		if (dot > 0) 
		{
			m_displayName.truncate(dot);
		}
	}
	
	m_shlibNameRE = QRegExp(cf.readEntry(ShlibRE));
	cf.readListEntry(EnableBuiltin, m_enabledBuiltins);
	
	QString printQString = cf.readEntry(PrintQStringCmd);
	const char* ascii = printQString.ascii();
	if (ascii == 0)
		ascii = "";
	m_printQStringDataCmd = new char[strlen(ascii)+1];
	strcpy(m_printQStringDataCmd, ascii);
	
	/*
	* Get the types. We search for entries of kind Types1, Types2, etc.
	* because a single entry Types could get rather long for large
	* libraries.
	*/
	QStringList typeNames;
	QString typesEntry;
	for (int i = 1; ; i++) 
	{
		// next bunch of types
		cf.setGroup(TypeTableGroup);
		typesEntry.sprintf(TypesEntryFmt, i);
		if (!cf.hasKey(typesEntry))
			break;
		typeNames = cf.readListEntry(typesEntry, ',');
		
		// now read them
		QStringListIterator it(typeNames);
		while (it.hasNext())
		{
			QString current = it.next();
			cf.setGroup(current);
			// check if this is an alias
			QString alias = cf.readEntry(AliasEntry);
			if (alias.isEmpty()) 
			{
				readType(cf, current.toAscii().constData());
			} 
			else 
			{
				// look up the alias type and insert it
				TypeInfo* info = m_typeDict[alias];
				if (info == 0) 
				{
					TRACE(QString().sprintf("<%s>: alias %s not found",
								it.operator char*(), alias.data()));
				} 
				else 
				{
					m_aliasDict.insert(alias, info);
					TRACE(QString().sprintf("<%s>: alias <%s>",
								it.operator char*(), alias.data()));
				}
			}
		}
	} // for all Types%d
}

void TypeTable::readType(KConfigBase& cf, const char* type)
{
	// the display string
	QString expr = cf.readEntry(DisplayEntry);
	
	TypeInfo* info = new TypeInfo(expr);
	if (info->m_numExprs == 0) 
	{
		TRACE(QString().sprintf("bogus type %s: no %% in Display: '%s'",
				type, expr.data()));
		delete info;
		return;
	}
	
	// Expr1, Expr2, etc...
	QString exprEntry;
	QString funcGuardEntry;
	for (int j = 0; j < info->m_numExprs; j++) 
	{
		exprEntry.sprintf(ExprEntryFmt, j+1);
		expr = cf.readEntry(exprEntry, QString());
		info->m_exprStrings[j] = expr;
		
		funcGuardEntry.sprintf(FunctionGuardEntryFmt, j+1);
		expr = cf.readEntry(funcGuardEntry, QString());
		info->m_guardStrings[j] = expr;
	}
	
	// add the new type
	m_typeDict.insert(type, info);
	TRACE(QString().sprintf("<%s>: %d exprs", type,	info->m_numExprs));
}

void TypeTable::copyTypes(Q3Dict<TypeInfo>& dict)
{
    for (Q3DictIterator<TypeInfo> it = m_typeDict; it != 0; ++it) 
	{
		dict.insert(it.currentKey(), it);
    }
    for (Q3DictIterator<TypeInfo> it = m_aliasDict; it != 0; ++it) 
	{
		dict.insert(it.currentKey(), it);
    }
}

bool TypeTable::isEnabledBuiltin(const char* feature)
{
	QListIterator<QString> it(m_enabledBuiltins);
	while (it.hasNext())
	{
		const QString& f = it.next();
		if (strcmp(feature, f.toAscii().constData()) == 0)
	    	return true;
	}
	return false;
}

TypeInfo::TypeInfo(const QString& displayString)
{
	// decompose the input into the parts
	int i = 0;
	int startIdx = 0;
	int idx;
	while (i < typeInfoMaxExpr && (idx = displayString.find('%', startIdx)) >= 0)
	{
		m_displayString[i] = displayString.mid(startIdx, idx-startIdx);
		startIdx = idx+1;
		i++;
	}
	m_numExprs = i;
	/*
	* Remaining string; note that there's one more display string than
	* sub-expressions.
	*/
	m_displayString[i] = displayString.right(displayString.length()-startIdx);
}

TypeInfo::~TypeInfo()
{
}


ProgramTypeTable::ProgramTypeTable() :
	m_parseQt2QStrings(false),
	m_QCharIsShort(false),
	m_printQStringDataCmd(0)
{
	m_types.setAutoDelete(false);	/* paranoia */
	m_aliasDict.setAutoDelete(false);	/* paranoia */
}

ProgramTypeTable::~ProgramTypeTable()
{
}

void ProgramTypeTable::clear()
{
    m_types.clear();
}

void ProgramTypeTable::loadTypeTable(TypeTable* table)
{
	table->copyTypes(m_types);
	// check whether to enable builtin QString support
	if (!m_parseQt2QStrings) 
	{
		m_parseQt2QStrings = table->isEnabledBuiltin("QString::Data");
	}
	if (!m_QCharIsShort) 
	{
		m_QCharIsShort = table->isEnabledBuiltin("QCharIsShort");
	}
	if (!m_printQStringDataCmd && *table->printQStringDataCmd()) 
	{
		m_printQStringDataCmd = table->printQStringDataCmd();
	}
}

TypeInfo* ProgramTypeTable::lookup(const char* type)
{
	TypeInfo* result = m_types[type];
	if (result == 0) 
	{
		result = m_aliasDict[type];
	}
	return result;
}

void ProgramTypeTable::registerAlias(const QString& name, TypeInfo* type)
{
	ASSERT(lookup(name.toAscii().constData()) == 0 || lookup(name.toAscii().constData()) == type);
	m_aliasDict.insert(name, type);
}

void ProgramTypeTable::loadLibTypes(const QStringList& libs)
{
	QStringListIterator it(libs);
	
	/*
	* We use a copy of the list of known libraries, from which we delete
	* those libs that we already have added. This way we avoid to load a
	* library twice.
	*/
	QList<TypeTable*> allTables = typeTables;	/* shallow copy! */
	QMutableListIterator<TypeTable*> itt (allTables);
	while (it.hasNext())
	{
		const QString& lib = it.next();
		itt.toFront();
		// look up the library
		while (itt.hasNext())
		{
			TypeTable* t = itt.next();
			if (t->matchFileName(lib.toAscii().constData()))
			{
				TRACE("adding types for " + lib);
				loadTypeTable(t);
				// remove the table
				itt.remove();
				break;
			}
		}
	}
}
